﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using Microsoft.SharePoint;

namespace SecurityParser
{
    public abstract class SecurableObject : DeployableItem
    {
        [XmlArray("RoleAssignments")]
        [XmlArrayItem("RoleAssignment")]
        public List<RoleAssignment> RoleAssignments
        {
            get;
            set;
        }

        [XmlAttribute("url")]
        public string Url
        {
            get;
            set;
        }

        [XmlAttribute("clearexisting")]
        public bool ClearExisting
        {
            get;
            set;
        }

        [XmlAttribute("inheritpermissions")]
        public bool InheritPermissions
        {
            get;
            set;
        }

        [XmlElement("List")]
        public List<List> Lists
        {
            get;
            set;
        }

        [XmlElement("ListItem")]
        public List<ListItem> ListItems
        {
            get;
            set;
        }

        [XmlElement("Web")]
        public List<Web> Webs
        {
            get;
            set;
        }

        public virtual void Parse(SPWeb web)
        {
            //if this item should be deployed in the current configration
            if (this.ShouldDeploy())
            {
                //pick up a reference to the underlying ISecurableObject refered to by URL
                ISecurableObject theObject = GetISecurableObject(web);

                if (theObject != null)
                {
                    //If the object is the root web then inheritance can't be changed
                    if (!IsRootWeb(web, theObject))
                    {
                        //Setup permission inheritance
                        if (InheritPermissions)
                        {
                            //If we're using unique permissions
                            if (theObject.HasUniqueRoleAssignments)
                            {
                                //Inherit from parent. Role Assignments for this item are ignored since they'll be inherited
                                theObject.ResetRoleInheritance();
                            }
                        }
                        else
                        {
                            BreakInheritance(web, theObject);

                            SetUpRoleAssignments(theObject, web);
                        }
                    }
                    else
                    {
                        //Since this is the root web. Everything inherits from here by default
                        //Therefore we need to apply any role assignments that are defined in the config file
                        SetUpRoleAssignments(theObject, web);
                    }

                    //Apply permissions on all child objects
                    ParseChildObjects(web);
                }
                else
                {
                    //Url in the configuration file didn't translate to a securable object
                    throw new InvalidOperationException(string.Format("Item at URL:{0} does not implement the ISecurableObject interface", this.Url));
                }
            }
        }

        private static bool IsRootWeb(SPWeb web, ISecurableObject theObject)
        {

            if (theObject is SPWeb)
            {
                SPWeb currentWeb = theObject as SPWeb;
                if (currentWeb == web.Site.RootWeb)
                {
                    return true;
                }
            }
            return false;
        }

        private ISecurableObject GetISecurableObject(SPWeb web)
        {
            ISecurableObject theObject;
            object obj;

            try
            {
                obj = web.GetObject(Url);
            }
            catch (FileNotFoundException ex)
            {
                //Object wasn't found.
                throw new FileNotFoundException("Object not found in SharePoint site", Url, ex);
            }

            if (obj is SPFolder)
            {
                SPFolder folder = obj as SPFolder;

                if (folder.ParentListId != new Guid())
                {
                    theObject = folder.ParentWeb.Lists[folder.ParentListId] as ISecurableObject;
                }
                else
                {
                    theObject = folder.ParentWeb as ISecurableObject;
                }
            }
            else if (obj is SPFile)
            {
                SPFile file = obj as SPFile;
                theObject = file.Item as ISecurableObject;
            }
            else
            {
                theObject = (ISecurableObject)web.GetObject(Url);
            }


            return theObject;
        }

        private void BreakInheritance(SPWeb web, ISecurableObject theObject)
        {
            web.AllowUnsafeUpdates = true;
            theObject.BreakRoleInheritance(!ClearExisting);
            //Breaking inheritance causes AllowUnsafeUpdates to revert
            web.AllowUnsafeUpdates = true;
        }

        private void ParseChildObjects(SPWeb web)
        {
            ParseWebs(web);
            ParseLists(web);
            ParseListItems(web);
        }

        private void ParseWebs(SPWeb web)
        {
            foreach (Web theWeb in Webs)
            {
                theWeb.Parse(web);
            }
        }

        private void ParseListItems(SPWeb web)
        {
            foreach (ListItem listItem in ListItems)
            {
                listItem.Parse(web);
            }
        }

        private void ParseLists(SPWeb web)
        {
            foreach (List list in Lists)
            {
                list.Parse(web);
            }
        }

        private void SetUpRoleAssignments(ISecurableObject item, SPWeb web)
        {
            //loop through our configured role assignments
            foreach (RoleAssignment assignment in RoleAssignments)
            {
                //Apply assignment to securable object
                assignment.Apply(item, web);
            }
        }
    }
}
